<!--
 * @Author: weisheng
 * @Date: 2023-04-05 21:32:56
 * @LastEditTime: 2023-06-04 23:21:52
 * @LastEditors: weisheng
 * @Description: 水印组件
 * @FilePath: \fant-mini-separate\uni_modules\hd-water-mark\components\hd-water-mark\hd-water-mark.vue
 * 记得注释
-->
<template>
	<view :class="rootClass" :style="rootStyle">
		<canvas v-if="!canvasOffScreenable && showCanvas" type="2d"
			:style="{ height: canvasHeight + 'px', width: canvasWidth + 'px', visibility: 'hidden' }"
			:canvas-id="canvasId" :id="canvasId" />
	</view>
</template>

<script lang="ts">
	export default {
		// 将自定义节点设置成虚拟的，更加接近Vue组件的表现，可以去掉微信小程序自定义组件多出的最外层标签
		options: {
			virtualHost: true
		}
	}
</script>

<script lang="ts" setup>
	import { computed, onMounted, ref, watch, nextTick } from 'vue'
	import { RegUtil } from '../../../hd-utils/functions/reg';
	import { CommonUtil } from '../../../hd-utils/utils/CommonUtil';

	/**
	 * WaterMark 水印
	 */
	interface Props {
		// 显示内容
		content ?: string
		// 显示图片的地址
		image ?: string
		// 图片高度
		imageHeight ?: number
		// 图片高度
		imageWidth ?: number
		// X轴间距，单位px
		gutterX ?: number
		// Y轴间距，单位px
		gutterY ?: number
		// canvas画布宽度，单位px
		width ?: number
		// canvas画布高度，单位px
		height ?: number
		// 是否为全屏水印
		fullScreen ?: boolean
		// 水印字体颜色
		color ?: string
		// 水印字体大小，单位px
		size ?: number
		// 水印字体样式（仅微信和h5支持），可能的值：normal、italic、oblique
		fontStyle ?: string
		// 水印字体的粗细（仅微信和h5支持）
		fontWeight ?: number | string
		// 水印字体系列（仅微信和h5支持）
		fontFamily ?: string
		// 水印旋转角度
		rotate ?: number
		// 自定义层级
		zIndex ?: number
		// 自定义透明度，取值 0~1
		opacity ?: number
	}

	const props = withDefaults(defineProps<Props>(), {
		// 显示内容
		content: '',
		// 显示图片
		image: '',
		// 图片高度
		imageHeight: 64,
		// 图片高度
		imageWidth: 120,
		// X轴间距
		gutterX: 24,
		// Y轴间距
		gutterY: 48,
		// canvas画布宽度，单位px
		width: 120,
		// canvas画布高度，单位px
		height: 64,
		// 是否为全屏水印
		fullScreen: true,
		// 水印字体颜色
		color: '#1a1a1a',
		// 水印字体大小
		size: 14,
		// 水印字体样式，可能的值：normal、italic、oblique
		fontStyle: 'normal',
		// 水印字体的粗细
		fontWeight: 'normal',
		// 水印字体系列
		fontFamily: 'PingFang SC',
		// 水印旋转角度
		rotate: -25,
		// 自定义层级
		zIndex: 1100,
		// 自定义透明度，取值 0~1
		opacity: 0.5
	})

	watch(
		() => props,
		() => {
			doReset()
		},
		{ deep: true }
	)

	const canvasId = ref<string>(`water${CommonUtil.s4()}`) // canvas 组件的唯一标识符
	const waterMarkUrl = ref<string>('') // canvas生成base64水印
	const canvasOffScreenable = ref<boolean>(uni.canIUse('createOffscreenCanvas') && Boolean(uni.createOffscreenCanvas)) // 是否可以使用离屏canvas
	const pixelRatio = ref<number>(uni.getSystemInfoSync().pixelRatio) // 像素比
	const canvasHeight = ref<number>((props.height + props.gutterY) * pixelRatio.value) // canvas画布高度
	const canvasWidth = ref<number>((props.width + props.gutterX) * pixelRatio.value) // canvas画布宽度
	const showCanvas = ref<boolean>(true) // 是否展示canvas

	/**
	 * 水印css类
	 */
	const rootClass = computed(() => {
		let classess : string = 'hd-water-mark'
		if (props.fullScreen) {
			classess = `${classess} hd-water-mark-full-screen`
		}
		return classess
	})

	/**
	 * 水印样式
	 */
	const rootStyle = computed(() => {
		const style : Record<string, string | number> = {
			opacity: props.opacity,
			backgroundSize: CommonUtil.addUnit(props.width + props.gutterX, 'px')
		}
		if (waterMarkUrl.value) {
			style['backgroundImage'] = `url('${waterMarkUrl.value}')`
		}
		return CommonUtil.style(style)
	})

	onMounted(() => {
		doInit()
	})

	function doReset() {
		showCanvas.value = true
		canvasHeight.value = (props.height + props.gutterY) * pixelRatio.value
		canvasWidth.value = (props.width + props.gutterX) * pixelRatio.value
		nextTick(() => {
			doInit()
		})
	}

	function doInit() {
		// #ifdef H5
		// h5使用document.createElement创建canvas，不用展示canvas标签
		showCanvas.value = false
		// #endif
		const { width, height, color, size, fontStyle, fontWeight, fontFamily, content, rotate, gutterX, gutterY, image, imageHeight, imageWidth } = props

		// 创建水印
		createWaterMark(width, height, color, size, fontStyle, fontWeight, fontFamily, content, rotate, gutterX, gutterY, image, imageHeight, imageWidth)
	}

	/**
	 * 创建水印图片
	 * @param width canvas宽度
	 * @param height canvas高度
	 * @param color canvas字体颜色
	 * @param size canvas字体大小
	 * @param fontStyle canvas字体样式
	 * @param fontWeight canvas字体字重
	 * @param fontFamily canvas字体系列
	 * @param content canvas内容
	 * @param rotate 倾斜角度
	 * @param gutterX X轴间距
	 * @param gutterY Y轴间距
	 * @param image canvas图片
	 * @param imageHeight canvas图片高度
	 * @param imageWidth canvas图片宽度
	 */
	function createWaterMark(
		width : number,
		height : number,
		color : string,
		size : number,
		fontStyle : string,
		fontWeight : number | string,
		fontFamily : string,
		content : string,
		rotate : number,
		gutterX : number,
		gutterY : number,
		image : string,
		imageHeight : number,
		imageWidth : number
	) {
		const canvasHeight = (height + gutterY) * pixelRatio.value
		const canvasWidth = (width + gutterX) * pixelRatio.value
		const contentWidth = width * pixelRatio.value
		const contentHeight = height * pixelRatio.value
		const fontSize = size * pixelRatio.value
		// #ifndef H5
		if (canvasOffScreenable.value) {
			createOffscreenCanvas(
				canvasHeight,
				canvasWidth,
				contentWidth,
				contentHeight,
				rotate,
				fontSize,
				fontFamily,
				fontStyle,
				fontWeight,
				color,
				content,
				image,
				imageHeight,
				imageWidth
			)
		} else {
			createCanvas(canvasHeight, contentWidth, rotate, fontSize, color, content, image, imageHeight, imageWidth)
		}
		// #endif
		// #ifdef H5
		createH5Canvas(
			canvasHeight,
			canvasWidth,
			contentWidth,
			contentHeight,
			rotate,
			fontSize,
			fontFamily,
			fontStyle,
			fontWeight,
			color,
			content,
			image,
			imageHeight,
			imageWidth
		)
		// #endif
	}

	/**
	 * 创建离屏canvas
	 * @param canvasHeight canvas高度
	 * @param canvasWidth canvas宽度
	 * @param contentWidth 内容宽度
	 * @param contentHeight 内容高度
	 * @param rotate 内容倾斜角度
	 * @param fontSize 字体大小
	 * @param fontFamily 字体系列
	 * @param fontStyle 字体样式
	 * @param fontWeight 字体字重
	 * @param color 字体颜色
	 * @param content 内容
	 * @param image canvas图片
	 * @param imageHeight canvas图片高度
	 * @param imageWidth canvas图片宽度
	 */
	function createOffscreenCanvas(
		canvasHeight : number,
		canvasWidth : number,
		contentWidth : number,
		contentHeight : number,
		rotate : number,
		fontSize : number,
		fontFamily : string,
		fontStyle : string,
		fontWeight : string | number,
		color : string,
		content : string,
		image : string,
		imageHeight : number,
		imageWidth : number
	) {
		// 创建离屏canvas
		const canvas : any = uni.createOffscreenCanvas({ height: canvasHeight, width: canvasWidth, type: '2d' })
		const ctx : any = canvas.getContext('2d')
		if (ctx) {
			if (image) {
				const img = canvas.createImage() as HTMLImageElement
				drawImageOffScreen(ctx, img, image, imageHeight, imageWidth, rotate, contentWidth, contentHeight, canvas)
			} else {
				drawTextOffScreen(ctx, content, contentWidth, contentHeight, rotate, fontSize, fontFamily, fontStyle, fontWeight, color, canvas)
			}
		} else {
			console.error('无法获取canvas上下文，请确认当前环境是否支持canvas')
		}
	}

	/**
	 * 非H5创建canvas
	 * 不支持创建离屏canvas时调用
	 * @param contentHeight 内容高度
	 * @param contentWidth 内容宽度
	 * @param rotate 内容倾斜角度
	 * @param fontSize 字体大小
	 * @param color 字体颜色
	 * @param content 内容
	 * @param image canvas图片
	 * @param imageHeight canvas图片高度
	 * @param imageWidth canvas图片宽度
	 */
	function createCanvas(
		contentHeight : number,
		contentWidth : number,
		rotate : number,
		fontSize : number,
		color : string,
		content : string,
		image : string,
		imageHeight : number,
		imageWidth : number
	) {
		const ctx = uni.createCanvasContext(canvasId.value)
		if (ctx) {
			if (image) {
				drawImageOnScreen(ctx, image, imageHeight, imageWidth, rotate, contentWidth, contentHeight)
			} else {
				drawTextOnScreen(ctx, content, contentWidth, rotate, fontSize, color)
			}
		} else {
			console.error('无法获取canvas上下文，请确认当前环境是否支持canvas')
		}
	}

	/**
	 * h5创建canvas
	 * @param canvasHeight canvas高度
	 * @param canvasWidth canvas宽度
	 * @param contentWidth 水印内容宽度
	 * @param contentHeight 水印内容高度
	 * @param rotate 水印内容倾斜角度
	 * @param fontSize 水印字体大小
	 * @param fontFamily 水印字体系列
	 * @param fontStyle 水印字体样式
	 * @param fontWeight 水印字体字重
	 * @param color 水印字体颜色
	 * @param content 水印内容
	 */
	function createH5Canvas(
		canvasHeight : number,
		canvasWidth : number,
		contentWidth : number,
		contentHeight : number,
		rotate : number,
		fontSize : number,
		fontFamily : string,
		fontStyle : string,
		fontWeight : string | number,
		color : string,
		content : string,
		image : string,
		imageHeight : number,
		imageWidth : number
	) {
		const canvas = document.createElement('canvas')
		const ctx = canvas.getContext('2d')
		canvas.setAttribute('width', `${canvasWidth}px`)
		canvas.setAttribute('height', `${canvasHeight}px`)
		if (ctx) {
			if (image) {
				const img = new Image()
				drawImageOffScreen(ctx, img, image, imageHeight, imageWidth, rotate, contentWidth, contentHeight, canvas)
			} else {
				drawTextOffScreen(ctx, content, contentWidth, contentHeight, rotate, fontSize, fontFamily, fontStyle, fontWeight, color, canvas)
			}
		} else {
			console.error('无法获取canvas上下文，请确认当前环境是否支持canvas')
		}
	}

	/**
	 * 绘制离屏文字canvas
	 * @param ctx canvas上下文
	 * @param content 水印内容
	 * @param contentWidth 水印宽度
	 * @param contentHeight 水印高度
	 * @param rotate 水印内容倾斜角度
	 * @param fontSize 水印字体大小
	 * @param fontFamily 水印字体系列
	 * @param fontStyle 水印字体样式
	 * @param fontWeight 水印字体字重
	 * @param color 水印字体颜色
	 * @param canvas canvas实例
	 */
	function drawTextOffScreen(
		ctx : CanvasRenderingContext2D,
		content : string,
		contentWidth : number,
		contentHeight : number,
		rotate : number,
		fontSize : number,
		fontFamily : string,
		fontStyle : string,
		fontWeight : string | number,
		color : string,
		canvas : HTMLCanvasElement
	) {
		ctx.textBaseline = 'middle'
		ctx.textAlign = 'center'
		ctx.translate(contentWidth / 2, contentWidth / 2)
		ctx.rotate((Math.PI / 180) * rotate)
		ctx.font = `${fontStyle} normal ${fontWeight} ${fontSize}px/${contentHeight}px ${fontFamily}`
		ctx.fillStyle = color
		ctx.fillText(content, 0, 0)
		ctx.restore()
		waterMarkUrl.value = canvas.toDataURL()
	}

	/**
	 * 绘制在屏文字canvas
	 * @param ctx canvas上下文
	 * @param content 水印内容
	 * @param contentWidth 水印宽度
	 * @param rotate 水印内容倾斜角度
	 * @param fontSize 水印字体大小
	 * @param color 水印字体颜色
	 */
	function drawTextOnScreen(ctx : UniApp.CanvasContext, content : string, contentWidth : number, rotate : number, fontSize : number, color : string) {
		ctx.setTextBaseline('middle')
		ctx.setTextAlign('center')
		ctx.translate(contentWidth / 2, contentWidth / 2)
		ctx.rotate((Math.PI / 180) * rotate)
		ctx.setFillStyle(color)
		ctx.setFontSize(fontSize)
		ctx.fillText(content, 0, 0)
		ctx.restore()
		ctx.draw()
			// #ifdef MP-DINGTALK
			// 钉钉小程序的canvasToTempFilePath接口与其他平台不一样
			; (ctx as any).toTempFilePath({
				success(res) {
					showCanvas.value = false
					waterMarkUrl.value = res.filePath
				}
			})
		// #endif
		// #ifndef MP-DINGTALK
		uni.canvasToTempFilePath({
			canvasId: canvasId.value,
			success: (res) => {
				showCanvas.value = false
				waterMarkUrl.value = res.tempFilePath
			}
		})
		// #endif
	}

	/**
	 * 绘制离屏图片canvas
	 * @param ctx canvas上下文
	 * @param img 水印图片对象
	 * @param image 水印图片地址
	 * @param imageHeight 水印图片高度
	 * @param imageWidth 水印图片宽度
	 * @param rotate 水印内容倾斜角度
	 * @param contentWidth 水印宽度
	 * @param contentHeight 水印高度
	 * @param canvas canvas实例
	 */
	async function drawImageOffScreen(
		ctx : CanvasRenderingContext2D,
		img : HTMLImageElement,
		image : string,
		imageHeight : number,
		imageWidth : number,
		rotate : number,
		contentWidth : number,
		contentHeight : number,
		canvas : HTMLCanvasElement
	) {
		ctx.translate(contentWidth / 2, contentHeight / 2)
		ctx.rotate((Math.PI / 180) * Number(rotate))
		img.crossOrigin = 'anonymous'
		img.referrerPolicy = 'no-referrer'
		if (RegUtil.isBase64Image(image)) {
			img.src = image
		} else {
			img.src = CommonUtil.setUrlParams(image, {
				timestamp: `${new Date().getTime()}`
			})
		}

		img.onload = () => {
			ctx.drawImage(
				img,
				(-imageWidth * pixelRatio.value) / 2,
				(-imageHeight * pixelRatio.value) / 2,
				imageWidth * pixelRatio.value,
				imageHeight * pixelRatio.value
			)
			ctx.restore()
			waterMarkUrl.value = canvas.toDataURL()
		}
	}

	/**
	 * 绘制在屏图片canvas
	 * @param ctx canvas上下文
	 * @param image 水印图片地址
	 * @param imageHeight 水印图片高度
	 * @param imageWidth 水印图片宽度
	 * @param rotate 水印内容倾斜角度
	 * @param contentWidth 水印宽度
	 * @param contentHeight 水印高度
	 */
	function drawImageOnScreen(
		ctx : UniApp.CanvasContext,
		image : string,
		imageHeight : number,
		imageWidth : number,
		rotate : number,
		contentWidth : number,
		contentHeight : number
	) {
		ctx.translate(contentWidth / 2, contentHeight / 2)
		ctx.rotate((Math.PI / 180) * Number(rotate))

		ctx.drawImage(
			image,
			(-imageWidth * pixelRatio.value) / 2,
			(-imageHeight * pixelRatio.value) / 2,
			imageWidth * pixelRatio.value,
			imageHeight * pixelRatio.value
		)
		ctx.restore()
		ctx.draw(false, () => {
			// #ifdef MP-DINGTALK
			// 钉钉小程序的canvasToTempFilePath接口与其他平台不一样
			; (ctx as any).toTempFilePath({
				success(res) {
					showCanvas.value = false
					waterMarkUrl.value = res.filePath
				}
			})
			// #endif
			// #ifndef MP-DINGTALK
			uni.canvasToTempFilePath({
				canvasId: canvasId.value,
				success: (res) => {
					showCanvas.value = false
					waterMarkUrl.value = res.tempFilePath
				}
			})
			// #endif
		})
	}
</script>

<style lang="scss" scoped>
	.hd-water-mark {
		position: absolute;
		opacity: 0.1;
		z-index: 1010;
		left: 0;
		right: 0;
		top: 0;
		bottom: 0;
		pointer-events: none;
		background-repeat: repeat;

		&-full-screen {
			position: fixed;
		}
	}
</style>